/*
 * Copyright (c) 2021, the hapjs-platform Project Contributors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.hapjs.component.constants;

import java.util.Arrays;
import org.hapjs.common.utils.FloatUtil;

public class Spacing {

    /**
     * Spacing type that represents the left direction. E.g. {@code marginLeft}.
     */
    public static final int LEFT = 0;
    /**
     * Spacing type that represents the top direction. E.g. {@code marginTop}.
     */
    public static final int TOP = 1;
    /**
     * Spacing type that represents the right direction. E.g. {@code marginRight}.
     */
    public static final int RIGHT = 2;
    /**
     * Spacing type that represents the bottom direction. E.g. {@code marginBottom}.
     */
    public static final int BOTTOM = 3;
    /**
     * Spacing type that represents start direction e.g. left in left-to-right, right in
     * right-to-left.
     */
    public static final int START = 4;
    /**
     * Spacing type that represents end direction e.g. right in left-to-right, left in right-to-left.
     */
    public static final int END = 5;
    /**
     * Spacing type that represents horizontal direction (left and right). E.g. {@code
     * marginHorizontal}.
     */
    public static final int HORIZONTAL = 6;
    /**
     * Spacing type that represents vertical direction (top and bottom). E.g. {@code marginVertical}.
     */
    public static final int VERTICAL = 7;
    /**
     * Spacing type that represents all directions (left, top, right, bottom). E.g. {@code margin}.
     */
    public static final int ALL = 8;

    private static final int[] sFlagsMap = {
            1, /*LEFT*/ 2, /*TOP*/ 4, /*RIGHT*/ 8, /*BOTTOM*/ 16, /*START*/ 32, /*END*/ 64,
            /*HORIZONTAL*/
            128, /*VERTICAL*/ 256, /*ALL*/
    };

    private final float[] mSpacing = newFullSpacingArray();
    private int mValueFlags = 0;
    private float mDefaultValue;
    private boolean mHasAliasesSet;

    public Spacing() {
        this(0);
    }

    public Spacing(float defaultValue) {
        mDefaultValue = defaultValue;
    }

    private static float[] newFullSpacingArray() {
        return new float[] {
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
                FloatUtil.UNDEFINED,
        };
    }

    /**
     * Set a spacing value.
     *
     * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}, {@link
     *                    #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
     * @param value       the value for this direction
     * @return {@code true} if the spacing has changed, or {@code false} if the same value was already
     * set
     */
    public boolean set(int spacingType, float value) {
        boolean success = false;
        if (!FloatUtil.floatsEqual(mSpacing[spacingType], value)) {
            mSpacing[spacingType] = value;

            if (FloatUtil.isUndefined(value)) {
                mValueFlags &= ~sFlagsMap[spacingType];
            } else {
                mValueFlags |= sFlagsMap[spacingType];
            }

            mHasAliasesSet =
                    (mValueFlags & sFlagsMap[ALL]) != 0
                            || (mValueFlags & sFlagsMap[VERTICAL]) != 0
                            || (mValueFlags & sFlagsMap[HORIZONTAL]) != 0;
            success = true;
        }
        // 确保数据的一致性
        if (spacingType == ALL) {
            setFourEdges(value);
        } else if (spacingType == VERTICAL) {
            setVertical(value);
        } else if (spacingType == HORIZONTAL) {
            setHorizontal(value);
        }
        return success;
    }

    private void setFourEdges(float value) {
        setVertical(value);
        setHorizontal(value);
    }

    private void setVertical(float value) {
        mSpacing[TOP] = mSpacing[BOTTOM] = value;
        mValueFlags |= sFlagsMap[TOP];
        mValueFlags |= sFlagsMap[BOTTOM];
    }

    private void setHorizontal(float value) {
        mSpacing[LEFT] = mSpacing[RIGHT] = value;
        mValueFlags |= sFlagsMap[LEFT];
        mValueFlags |= sFlagsMap[RIGHT];
    }

    /**
     * Get the spacing for a direction. This takes into account any default values that have been set.
     *
     * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}
     */
    public float get(int spacingType) {
        float defaultValue =
                (spacingType == START || spacingType == END ? FloatUtil.UNDEFINED : mDefaultValue);

        if (mValueFlags == 0) {
            return defaultValue;
        }

        if ((mValueFlags & sFlagsMap[spacingType]) != 0) {
            return mSpacing[spacingType];
        }

        if (mHasAliasesSet) {
            int secondType = spacingType == TOP || spacingType == BOTTOM ? VERTICAL : HORIZONTAL;
            if ((mValueFlags & sFlagsMap[secondType]) != 0) {
                return mSpacing[secondType];
            } else if ((mValueFlags & sFlagsMap[ALL]) != 0) {
                return mSpacing[ALL];
            }
        }

        return defaultValue;
    }

    /**
     * Get the raw value (that was set using {@link #set(int, float)}), without taking into account
     * any default values.
     *
     * @param spacingType one of {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, {@link #BOTTOM}, {@link
     *                    #VERTICAL}, {@link #HORIZONTAL}, {@link #ALL}
     */
    public float getRaw(int spacingType) {
        return mSpacing[spacingType];
    }

    /**
     * Resets the spacing instance to its default state. This method is meant to be used when
     * recycling {@link Spacing} instances.
     */
    public void reset() {
        Arrays.fill(mSpacing, FloatUtil.UNDEFINED);
        mHasAliasesSet = false;
        mValueFlags = 0;
    }

    /**
     * Try to get start value and fallback to given type if not defined. This is used privately by the
     * layout engine as a more efficient way to fetch direction-aware values by avoid extra method
     * invocations.
     */
    float getWithFallback(int spacingType, int fallbackType) {
        return (mValueFlags & sFlagsMap[spacingType]) != 0 ? mSpacing[spacingType] :
                get(fallbackType);
    }
}
